home *** CD-ROM | disk | FTP | other *** search
/ Internet Info 1994 March / Internet Info CD-ROM (Walnut Creek) (March 1994).iso / networking / mail / listserv / utils / expn.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-06-01  |  10.8 KB  |  473 lines

  1. /*-
  2.  * $Id: expn.c,v 1.9 1992/05/31 03:43:02 dupuy Exp $
  3.  *
  4.  *    To compile with DNS support, cc -O -o expn expn.c -lresolv
  5.  *    To compile without DNS support, cc -O -o expn -DNODOMAINS expn.c
  6.  *     To use, expn user@host
  7.  *        exit codes: 0 = valid address
  8.  *                1 = system error (address may be valid)
  9.  *                2 = SMTP server error (address may be valid)
  10.  *                3 = invalid user
  11.  *                4 = invalid host
  12.  *
  13.  * This started out as mverify, by Jeff Beadles <jeff@quark.WV.TEK.COM>
  14.  * with a couple of lines of autobounce.c by Pete Shipley <shipley@berkley.edu>
  15.  *
  16.  * I decided to enhance it for use in verifying mailing lists, requiring better
  17.  * support for all the varieties of RFC-821 mailers out there in the swamps.
  18.  * So I added support for MX records and real handling of RFC-821 result codes.
  19.  *
  20.  * If you think there's anything left to add, please send it to me, and I'll
  21.  * see about adding it.
  22.  *
  23.  * Alexander Dupuy <dupuy@cs.columbia.edu>
  24.  *
  25.  */
  26.  
  27. #ifdef BSD
  28. #include <strings.h>
  29. #else
  30. #include <string.h>
  31. #include <memory.h>
  32. #define index strchr
  33. #define bcopy(b1,b2,len) memcpy(b2,b1,len)
  34. #endif
  35.  
  36. #include <sys/types.h>
  37. #include <sys/socket.h>
  38. #include <netdb.h>
  39. #include <netinet/in.h>
  40. #ifndef NODOMAINS
  41. #include <arpa/nameser.h>
  42. #include <resolv.h>
  43. #endif
  44. #include <stdio.h>
  45.  
  46. #define MAXMXHOSTS 20
  47.  
  48. #define SYSERR 1
  49. #define SMTPERR 2
  50. #define BADUSER 3
  51. #define BADHOST 4
  52.  
  53. char *fgets ();
  54. char *index ();
  55. struct hostent *gethostbyname ();
  56.  
  57. main (argc, argv)
  58.     int argc;
  59.     char **argv;
  60. {
  61.     int smtpfd;
  62.     FILE *fin;                /* separate stdio streams to allow */
  63.     FILE *fout;                /* mixed reads and writes w/o seeks */
  64.     char buffer[2048];            /* stdio line buffer */
  65.  
  66.     char *user;                /* pieces of argv */
  67.     char *orighost;
  68.     char *host;
  69.  
  70.     char *command;            /* for expn/vrfy smtp commands */
  71.     int exitstatus;
  72.  
  73.     struct hostent *hp;            /* inet networking */
  74.     struct servent *sp;
  75.     struct sockaddr_in server;
  76.  
  77.     char *bp;                /* utility infielder */
  78.  
  79. #ifndef NODOMAINS
  80.     int n;                /* utility index */
  81.  
  82.     HEADER *dhp;            /* DNS reply header and fields*/
  83.     int ancount;
  84.     int qdcount;
  85.  
  86.     union                /* DNS reply and pointers */
  87.     {
  88.     HEADER hdr;
  89.     u_char bytes[PACKETSZ];
  90.     }     answer;
  91.     u_char *cp;
  92.     u_char *eom;
  93.  
  94.     char hostbuf[PACKETSZ];        /* hostnames buffer */
  95.     int buflen;
  96.  
  97.     u_short pref;
  98.     u_short type;
  99.  
  100.     char *hosts[MAXMXHOSTS];        /* arrays for multiple MX hosts */
  101.     u_short prefs[MAXMXHOSTS];
  102.     u_long bestpref;
  103.     int besthost;            /* indexes into arrays */
  104.     int i;
  105. #endif
  106.  
  107.     if (argc != 2)
  108.     {
  109.     (void) fprintf (stderr, "Usage: %s user@host\n", argv[0]);
  110.     return (SYSERR);
  111.     }
  112.  
  113.     user = argv[1];
  114.     if ((orighost = index (user, '@')) == 0)
  115.     host = "localhost";
  116.     else
  117.     {
  118.     *orighost = '\0';
  119.     orighost++;
  120.     host = orighost;
  121.     }
  122.  
  123.     server.sin_family = AF_INET;
  124.  
  125.     /*
  126.      * Get the smtp port using tcp.
  127.      */
  128.  
  129.     sp = getservbyname ("smtp", "tcp");
  130.     server.sin_port = sp->s_port;
  131.     if (!server.sin_port)
  132.     {
  133.     (void) fprintf (stderr, "unknown service: smtp/tcp\n");
  134.     return (SYSERR);
  135.     }
  136.  
  137. #ifdef NODOMAINS
  138.  
  139.     /*
  140.      * Now get the information for the @host part of the address.
  141.      */
  142.  
  143.     if ((hp = gethostbyname (host)) == 0)
  144.     {
  145.     (void) fprintf (stderr, "%s: unknown host\n", host);
  146.     return (BADHOST);
  147.     }
  148.  
  149. #else
  150.  
  151.     besthost = -1;            /* don't retry MX */
  152.  
  153.     /*
  154.      * Check MX records for the @host part of the address.
  155.      */
  156.  
  157.     n = res_search (host, C_IN, T_MX, answer.bytes, sizeof (answer));
  158.     if (n < 0)
  159.     goto punt;
  160.  
  161.     /* find first satisfactory answer */
  162.  
  163.     dhp = &answer.hdr;
  164.     cp = answer.bytes + sizeof (HEADER);
  165.     eom = answer.bytes + n;
  166.     for (qdcount = ntohs (dhp->qdcount); qdcount--; cp += n + QFIXEDSZ)
  167.     if ((n = dn_skipname (cp, eom)) < 0)
  168.         goto punt;
  169.  
  170.     /* copy MX hosts and preferences into arrays */
  171.  
  172.     buflen = sizeof (hostbuf);
  173.     bp = hostbuf;
  174.     ancount = ntohs (dhp->ancount);
  175.     i = 0;
  176.     while (--ancount >= 0 && cp < eom && i < MAXMXHOSTS)
  177.     {
  178.     if ((n = dn_expand (&answer.hdr, eom, cp, bp, buflen)) < 0)
  179.         break;
  180.     cp += n;
  181.     GETSHORT (type, cp);
  182.     cp += sizeof (u_short) + sizeof (u_long);
  183.     GETSHORT (n, cp);
  184.     if (type != T_MX)
  185.     {
  186.         cp += n;
  187.         continue;
  188.     }
  189.     GETSHORT (pref, cp);
  190.     if ((n = dn_expand (&answer.hdr, eom, cp, bp, buflen)) < 0)
  191.         break;
  192.     cp += n;
  193.  
  194.     prefs[i] = pref;
  195.     hosts[i] = bp;
  196.     i++;
  197.  
  198.     n = strlen (bp) + 1;
  199.     bp += n;
  200.     buflen -= n;
  201.     }
  202.     hosts[i] = 0;            /* terminate host array */
  203.  
  204.   nextmxhost:
  205.  
  206.     i = 0;
  207.     n = 0;
  208.     bestpref = 65536;
  209.  
  210.     while (hosts[i] && i < MAXMXHOSTS)
  211.     {
  212.     if (prefs[i] < 65535)        /* count untried MX hosts */
  213.         n++;
  214.     if (prefs[i] < bestpref)
  215.     {
  216.         bestpref = prefs[i];
  217.         host = hosts[i];
  218.         besthost = i;
  219.     }
  220.     i++;
  221.     }
  222.     if (n <= 1)                /* don't retry if this is last host */
  223.     besthost = -1;
  224.     
  225.   punt:
  226.  
  227.     /*
  228.      * Now get the information for the @host part of the address.
  229.      */
  230.  
  231.     if ((hp = gethostbyname (host)) == 0)
  232.     {
  233.     herror (host);
  234.     if (besthost >= 0)
  235.     {
  236.         prefs[besthost] = 65535;
  237.         goto nextmxhost;
  238.     }
  239.     if (h_errno == TRY_AGAIN)
  240.         return (SYSERR);
  241.  
  242.     return (BADHOST);
  243.     }
  244.  
  245. #endif
  246.  
  247.   reconnect:
  248.  
  249.     (void) bcopy (hp->h_addr, (char *) &server.sin_addr, hp->h_length);
  250.  
  251.     /*
  252.      * One socket please...
  253.      */
  254.  
  255.     if ((smtpfd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
  256.     {
  257.     perror ("socket");
  258.     return (SYSERR);
  259.     }
  260.  
  261.     /*
  262.      * Connecting to the socket might make things go a little easier...  :-)
  263.      */
  264.  
  265.     while (connect (smtpfd, (struct sockaddr *) &server, sizeof (server)) < 0)
  266.     {
  267. #ifdef h_addr
  268.     if (*++hp->h_addr_list)
  269.     {
  270.         (void) close (smtpfd);
  271.         goto reconnect;
  272.     }
  273. #endif
  274.     perror (host);
  275.     if (besthost >= 0)
  276.     {
  277.         prefs[besthost] = 65535;
  278.         (void) close (smtpfd);
  279.         goto nextmxhost;
  280.     }
  281.     return (SYSERR);
  282.     }
  283.  
  284.     /*
  285.      * Change them to streams 'cause I like 'em better.
  286.      */
  287.  
  288.     fout = fdopen (smtpfd, "w");
  289.     fin = fdopen (smtpfd, "r");
  290.  
  291.     /*
  292.      * The format of SMTP reply codes is a three digit number followed by
  293.      * either space or '-' minus.  The minus code indicates a multi-line
  294.      * response, so we keep reading lines until we see one with a space.
  295.      *
  296.      * Reply codes beginning with 2 are positive, and can be ignored
  297.      * (although 251 indicates a non-local user).
  298.      * Reply codes beginning with 4 indicate transient errors.
  299.      * Reply codes beginning with 5 indicate permanent errors
  300.      * (although 551 will return a forward-path for a non-local user).
  301.      *
  302.      * Note that RFC-1123 defines a new reply code 252 which indicates that
  303.      * the user cannot be verified, but the server will attempt forwarding.
  304.      * This is pretty much the same as 251 except there is less assurance that
  305.      * the address is valid.
  306.      */
  307.  
  308.     /*
  309.      * Wait for the smtp mailer to answer.  It should greet us with a 220 code
  310.      */
  311.  
  312.     while (fgets (buffer, sizeof (buffer) - 1, fin) != NULL)
  313.     {
  314.     if (buffer[3] == '-')
  315.         continue;
  316.  
  317.     if (!strncmp (buffer, "220 ", 4))
  318.         break;
  319.     else                /* some kind of mailer error */
  320.     {
  321.         (void) fputs (buffer, stderr);
  322.         (void) putc ('\n', stderr);    /* fputs doesn't add a newline */
  323.         exitstatus = SMTPERR;
  324.         goto done;
  325.     }
  326.     }
  327.  
  328.     exitstatus = 0;            /* let's be optimistic :-) */
  329.  
  330.     if (orighost)
  331.     command = "EXPN %s@%s\r\n";    /* RFC-821 requires CRLF */
  332.     else
  333.     command = "EXPN %s\r\n";
  334.  
  335.     /*
  336.      * Now that we have the mailer's attention, tell it to 'verify' the user.
  337.      * We have four ways of trying this, using EXPN or VRFY, and using @host
  338.      * or not.  We will try all of them (only two if no @host specified).
  339.      */
  340.  
  341.     (void) fprintf (fout, command, user, orighost);
  342.     (void) fflush (fout);
  343.  
  344.     /*
  345.      * Now just go into a loop reading responses from the mailer.
  346.      */
  347.  
  348.     while (fgets (buffer, sizeof (buffer) - 1, fin) != NULL)
  349.     {
  350.     /*
  351.      * Zap ^M from the lines.
  352.      */
  353.     if ((bp = index (buffer, '\r')) != 0)
  354.         *bp = '\0';
  355.  
  356.     if (!strncmp (buffer, "250", 3))
  357.     {
  358.         (void) puts (buffer + 4);    /* delete 250- code */
  359.     }
  360.     else if (!strncmp (buffer, "251", 3)) /* only given to VRFY */
  361.     {
  362.         (void) puts (buffer);    /* unusual - leave 251 code in */
  363.     }
  364.     else if (!strncmp (buffer, "252", 3)) /* only given to VRFY */
  365.     {                /* reconstruct the user name we sent */
  366.         if (orighost)
  367.         (void) printf ("<%s@%s>\n", user, orighost);
  368.         else
  369.         (void) puts (user);
  370.  
  371.         (void) fputs (buffer, stderr);
  372.         (void) putc ('\n', stderr);    /* print warning to stderr */
  373.     }
  374.     else if (!strncmp (buffer, "551", 3)) /* only given to VRFY */
  375.     {
  376.         (void) puts (buffer);    /* unusual - leave 551 code in */
  377.  
  378.         (void) fputs (buffer, stderr);
  379.         (void) putc ('\n', stderr);    /* print warning to stderr */
  380.     }
  381.     else if (buffer[0] == '5' && (buffer[1] == '0' || buffer[2] == '0'))
  382.     {                /* 50x syntax err; 550 list vs. user */
  383.         /*
  384.          * We try first with EXPN, then VRFY, since EXPN gives more info.
  385.          * A server which accepts EXPN but not VRFY can give misleading
  386.          * results for bad addresses.  The only server I have run across
  387.          * (@lists.psi.com) accepts VRFY but not EXPN, so this is okay.
  388.          * If some server does accept EXPN but not VRFY, then change this
  389.          * to retry only for 50x codes.  RFC-821 says that a VRFY on a list
  390.          * or an EXPN on a user is allowed to return 550, but I've never
  391.          * seen that happen.
  392.          */
  393.  
  394.         switch (exitstatus)
  395.         {
  396.           case 0:            /* first failure; try without @host */
  397.         if (orighost)        /* if we haven't already done so */
  398.         {
  399.             command = "EXPN %s\r\n";
  400.             exitstatus = 1;
  401.             break;
  402.         }
  403.         /* fall through if !orighost */
  404.  
  405.           case 1:            /* EXPN doesn't work; try VRFY */
  406.         if (orighost)
  407.         {
  408.             command = "VRFY %s@%s\r\n";
  409.             exitstatus = 2;
  410.             break;
  411.         }
  412.         /* fall through if !orighost */
  413.  
  414.           case 2:
  415.         command = "VRFY %s\r\n";
  416.         exitstatus = 3;
  417.         break;
  418.  
  419.           case 3:
  420.         if (buffer[1] == '5')    /* 55x implies bad username */
  421.             exitstatus = BADUSER;
  422.         else
  423.             exitstatus = SMTPERR;
  424.  
  425.         (void) fputs (buffer, stderr);
  426.         (void) putc ('\n', stderr);
  427.         goto done;        /* we've had enough by now */
  428.         }
  429.         
  430.         (void) fprintf (fout, command, user, orighost);
  431.         (void) fflush (fout);
  432.         continue;
  433.     }
  434.     else
  435.     {
  436.         (void) fputs (buffer, stderr);    /* failed bigtime */
  437.         (void) putc ('\n', stderr);    /* fputs doesn't add a newline */
  438.     }
  439.     
  440.     if (buffer[3] == ' ')        /* no more responses coming */
  441.     {
  442.         if (buffer[0] == '2')
  443.         exitstatus = 0;        /* the last try worked */
  444.         else if (buffer[0] == '5' && buffer[1] == '5')
  445.         exitstatus = BADUSER;    /* 55x implies bad username */
  446.         else
  447.         exitstatus = SMTPERR;    /* SMTP error */
  448.         break;
  449.     }
  450.     }
  451.  
  452.   done:
  453.  
  454.     /*
  455.      * Close the SMTP connection gracefully.
  456.      */
  457.  
  458.     (void) fputs ("QUIT\r\n", fout);
  459.     (void) fflush (fout);
  460.     (void) fclose (fout);
  461.     (void) fclose (fin);
  462.  
  463.     if (fflush (stdout))        /* in case we couldn't write output */
  464.     exitstatus = 1;
  465.  
  466.     /*
  467.      * And leave this nice program.
  468.      */
  469.  
  470.     return (exitstatus);
  471. }
  472.  
  473.